package com.ys.arch.splitamount.algorithm.core;

import com.ys.arch.splitamount.algorithm.execption.SplitAmountAlgorithmException;
import com.ys.arch.splitamount.algorithm.support.Pair;
import com.ys.arch.splitamount.algorithm.utils.AmountUtil;
import com.ys.arch.splitamount.algorithm.utils.LambdaOperateUtil;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * <p>@description: 单金额拆分算法</>
 *
 * @author shuang.yang
 * @version 1.0
 * @create 2021/12/25/17:54
 */
public class SplitSingleAmountHelper {


    /***
     * 金额拆分 后续会基于这个类做通用扩展  后续会使用 splitSingleAmountAndReturnNewAbsAmountMap
     * @param amount 待拆分的总金额
     * @param inputCalculateRateMap
     * @param <T>
     * @return
     */
    public static <T> Map<T, BigDecimal> splitSingleAmountAndReturnNewAbsAmountMap(BigDecimal amount, Map<T, BigDecimal/*amount*/> inputCalculateRateMap, Integer scale, RoundingMode roundingMode) {


        BigDecimal absAmount = AmountUtil.abs(amount);

        Map<Long/*新建下标*/, T/*输入的下标*/> map = new HashMap<>();
        Map<Long, BigDecimal> convertCalculateRateMap = new HashMap<>();
        Set<T> keySet = inputCalculateRateMap.keySet();
        long index = 0L;
        for (T t : keySet) {
            map.put(index, t);
            convertCalculateRateMap.put(index, inputCalculateRateMap.get(t));
            index++;
        }
        //这里面操作的都是绝对值,如果需要返回,需要返回是否正逆向的 需要根据negate 来判定
        Map<Long, BigDecimal> splitAmountMap = doSplitSingleAmountAndReturnNewMap(absAmount, convertCalculateRateMap, scale, roundingMode);

        return splitAmountMap.entrySet()
                .stream().collect(Collectors.toMap(entry -> {
                    Long currentIndex = entry.getKey();
                    return map.get(currentIndex);
                }, entry -> AmountUtil.abs(entry.getValue())));

    }


    /**
     * 这里是绝对值
     *
     * @param totalAmount
     * @param calculateRateAmountMap
     * @param roundingMode
     * @return
     */
    private static Map<Long/*新下标*/, BigDecimal/*对应金额*/> doSplitSingleAmountAndReturnNewMap(BigDecimal totalAmount, Map<Long, BigDecimal> calculateRateAmountMap, Integer scale, RoundingMode roundingMode) {

        //返回的占比后的金额
        Map<Long, BigDecimal> returnedMap = new HashMap<>();
        if (calculateRateAmountMap.entrySet().size() == 1) {
            List<Long> list = new ArrayList<>(calculateRateAmountMap.keySet());
            Long key = list.get(0);
            returnedMap.put(key, totalAmount);
            return returnedMap;
        }

        //排序转换数据
        List<Pair<Long/*下标*/, BigDecimal>> calculateRateAmountPairList = calculateRateAmountMap.entrySet()
                .stream()
                .map(entry -> Pair.of(entry.getKey(), entry.getValue()))
                //升序排序
                .sorted((Pair<Long, BigDecimal> a, Pair<Long, BigDecimal> b) -> a.getValue().compareTo(b.getValue()) >= 0 ? 1 : -1)
                .collect(Collectors.toList());
        //用于占比的总金额
        BigDecimal totalCalculateRateAmount = LambdaOperateUtil.accumulateListByCondition(calculateRateAmountMap.values(), AmountUtil::abs, null, BigDecimal.ZERO, BigDecimal::add);
        //累加的金额
        BigDecimal accAmount = BigDecimal.ZERO;
        //累计的费率
        BigDecimal accRate = new BigDecimal("0");
        int itemReqSize = calculateRateAmountPairList.size();


        for (int i = 0; i < itemReqSize; i++) {

            boolean isLastItem = (itemReqSize - 1 == i);
            Pair<Long, BigDecimal> pair = calculateRateAmountPairList.get(i);
            //当前计算出来的金额
            BigDecimal currentCalculatedAmount;
            if (isLastItem) {
                currentCalculatedAmount = AmountUtil.sub(totalAmount, accAmount);
                validateComparedWithZero(currentCalculatedAmount, "金额拆分-倒减异常");
            } else {
                BigDecimal currentCalculateRateAmount = pair.getRight();
                Pair<BigDecimal/*currentRate*/, BigDecimal/*accRate*/> calculateRatePair = SplitAlgorithmRateCalculateHelper.calculateRate(totalCalculateRateAmount, currentCalculateRateAmount, false, accRate);
                BigDecimal rate = calculateRatePair.getLeft();
                accRate = calculateRatePair.getRight();
                currentCalculatedAmount = AmountUtil.multiply(totalAmount, rate, scale, roundingMode);

            }
            accAmount = accAmount.add(currentCalculatedAmount);
            returnedMap.put(pair.getKey(), currentCalculatedAmount);
        }
        BigDecimal accAbsAmount = LambdaOperateUtil.accumulateListByCondition(returnedMap.values(), AmountUtil::abs, null, BigDecimal.ZERO, BigDecimal::add);
        BigDecimal absTotalAmount = AmountUtil.abs(totalAmount);
        if (AmountUtil.compareNotEqual(accAbsAmount, absTotalAmount, true)) {
            throw new SplitAmountAlgorithmException("金额拆分异常-输入的总金额与拆分后累计金额不等");
        }
        return returnedMap;
    }

    /**
     * 这里是绝对值
     *
     * @param totalAmount
     * @param calculateRateMap
     * @param roundingMode
     * @return
     */
    private static Map<Long/*新下标*/, BigDecimal/*对应金额*/> doSplitSingleAmountAndReturnNewMapByRate(BigDecimal totalAmount,
                                                                                                        Map<Long, BigDecimal> calculateRateMap,
                                                                                                        Integer scale,
                                                                                                        RoundingMode roundingMode) {

        //返回的占比后的金额
        Map<Long, BigDecimal> returnedMap = new HashMap<>();
        if (calculateRateMap.entrySet().size() == 1) {
            List<Long> list = new ArrayList<>(calculateRateMap.keySet());
            Long key = list.get(0);
            returnedMap.put(key, totalAmount);
            return returnedMap;
        }
        //排序转换数据
        List<Pair<Long/*下标*/, BigDecimal/*rate*/>> calculateRatePairList = calculateRateMap.entrySet()
                .stream()
                .map(entry -> Pair.of(entry.getKey(), entry.getValue()))
                //升序排序
                .sorted((Pair<Long, BigDecimal> a, Pair<Long, BigDecimal> b) -> a.getValue().compareTo(b.getValue()) >= 0 ? 1 : -1)
                .collect(Collectors.toList());
        //累加的金额

        //累计的费率
        int itemReqSize = calculateRatePairList.size();

        for (int i = 0; i < itemReqSize; i++) {

            boolean isLastItem = (itemReqSize - 1 == i);
            Pair<Long, BigDecimal> pair = calculateRatePairList.get(i);
            //当前计算出来的金额
            BigDecimal currentCalculatedAmount;
            if (isLastItem) {
                BigDecimal accAmount = returnedMap.values().stream().reduce(BigDecimal.ZERO, BigDecimal::add);
                currentCalculatedAmount = AmountUtil.sub(totalAmount, accAmount);
                validateComparedWithZero(currentCalculatedAmount, "金额拆分-倒减异常");
            } else {
                BigDecimal rate = pair.getRight();
                currentCalculatedAmount = AmountUtil.multiply(totalAmount, rate, scale, roundingMode);

            }

            returnedMap.put(pair.getKey(), currentCalculatedAmount);
        }
        BigDecimal accAbsAmount = LambdaOperateUtil.accumulateListByCondition(returnedMap.values(), AmountUtil::abs, null, BigDecimal.ZERO, BigDecimal::add);
        BigDecimal absTotalAmount = AmountUtil.abs(totalAmount);
        if (AmountUtil.compareNotEqual(accAbsAmount, absTotalAmount, true)) {
            throw new SplitAmountAlgorithmException("金额拆分异常-输入的总金额与拆分后累计金额不等");
        }
        return returnedMap;
    }

    public static void validateComparedWithZero(BigDecimal currentCalculateAmount, String text) {
        if (BigDecimal.ZERO.compareTo(currentCalculateAmount) > 0) {
            throw new SplitAmountAlgorithmException(text);
        }
    }
}
